﻿#include "YImageDrawer.h"
#include <QVector>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneMouseEvent>
#include <QMouseEvent>
#include <QTimer>
#include <QtMath>

#include <QDebug>
#define DP qDebug()
#define DF DP << __func__

namespace Y_Image_Drawer_GUI {
//class YGraphicsView: public QGraphicsView
//{
//public:
//    YGraphicsView(QGraphicsScene* scene, QWidget* parent): QGraphicsView(scene, parent) {}
//};
//class YGraphicsScene: public QGraphicsScene
//{
//public:
//    YGraphicsScene(const QRect& rect, QWidget* parent): QGraphicsScene(rect, parent) {}
//};
class YGraphicsChangeItem;
class YGraphicsItem{
public:
    virtual ~YGraphicsItem(){}
    virtual void rerect(const QRect& rect) {Q_UNUSED(rect);}
    virtual void rerect(const QPoint& pos){
        if(lastPos == pos) return;
        lastPos = pos;
        QRect rect;
        rect.setX(qMin(startPos.x(), pos.x()));
        rect.setY(qMin(startPos.y(), pos.y()));
        rect.setRight(qMax(startPos.x(), pos.x()));
        rect.setBottom(qMax(startPos.y(), pos.y()));
        rerect(rect);
    }
    virtual void reChangePos(){}
    virtual void showChange(){}
    virtual void hideChange(){}
    virtual void cursorChange(const QPointF&, YGraphicsChangeItem*){}
    bool inChangeHover();
public:
    QPoint startPos;
    QPoint lastPos;
    QVector<QPointF> changePos;
    QList<YGraphicsChangeItem*> changeItem;
    bool showChanged = false;
    bool inChangeArea = false;
    char AlignPadding[6];
    QPointF pressPos;
    int resizeType = 0;//0:移动,1:垂直水平,2:垂直,3:水平
    char AlignPadding2[4];
    YGraphicsChangeItem* cursorItem = nullptr;
    bool isArrow = false;
};
class YGraphicsChangeItem: public QGraphicsEllipseItem{
public:
    YGraphicsChangeItem():QGraphicsEllipseItem(QRect(-3, -3, 6, 6)){setBrush(Qt::white);setAcceptHoverEvents(true);}
    bool isHover = false;
    char AlignPadding[7];
    YGraphicsItem* item;
    QPointF pressPos;
    void hoverEnterEvent(QGraphicsSceneHoverEvent *){isHover = true;scene()->setProperty("inChangeArea", true);}
    void hoverLeaveEvent(QGraphicsSceneHoverEvent *){isHover = false;scene()->setProperty("inChangeArea", false);}
    void mousePressEvent(QGraphicsSceneMouseEvent *event){item->cursorChange(event->pos() + pos(), this);}
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event){item->cursorChange(event->pos() + pos(), this);}
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *){item->cursorChange(QPointF(), nullptr);}
};
bool YGraphicsItem::inChangeHover(){
    for(YGraphicsChangeItem* item : changeItem){
        if(item->isHover) return true;
    }
    return false;
}
class YGraphicsRectItem : public QGraphicsRectItem, public YGraphicsItem
{
public:
    void rerect(const QRect& rect){
        if(changePos.empty()){
            changePos.resize(8);
        }
        setPos(rect.topLeft());
        setRect(QRectF(0, 0, rect.width(), rect.height()));
    }
    void reChangePos(){
        QRectF rect(pos(), this->rect().size());
        changePos[0] = rect.topLeft();
        changePos[1] = rect.topRight();
        changePos[2] = rect.bottomLeft();
        changePos[3] = rect.bottomRight();
        changePos[4] = QPointF(rect.x() + rect.width() / 2.0, rect.y());
        changePos[5] = QPointF(rect.x() + rect.width() / 2.0, rect.bottom());
        changePos[6] = QPointF(rect.x(), rect.y() + rect.height() / 2.0);
        changePos[7] = QPointF(rect.right(), rect.y() + rect.height() / 2.0);
    }
    void showChange(){
        static QCursor cursors[8] = {Qt::SizeFDiagCursor, Qt::SizeBDiagCursor, Qt::SizeBDiagCursor, Qt::SizeFDiagCursor,
                                     Qt::SizeVerCursor, Qt::SizeVerCursor, Qt::SizeHorCursor, Qt::SizeHorCursor};
        if(changeItem.empty()){
            for(int i = 0; i < changePos.size(); ++i){
                YGraphicsChangeItem* ellipse = new YGraphicsChangeItem;
                changeItem.append(ellipse);
                ellipse->setCursor(cursors[i]);
                ellipse->item = this;
                scene()->addItem(ellipse);
            }
        }
        reChangePos();
        for(int i = 0; i < changePos.size(); ++i){
            changeItem[i]->setPos(changePos.at(i));
            QPen pen = this->pen();
            pen.setWidth(1);
            changeItem[i]->setPen(pen);
            changeItem[i]->show();
        }
        showChanged = true;
    }
    void hideChange(){
        for(int i = 0; i < changePos.size(); ++i){
            changeItem[i]->hide();
        }
        showChanged = false;
    }
    void cursorChange(const QPointF& cursorPressPos, YGraphicsChangeItem* cursorItem){
        if(this->cursorItem){
            if(cursorItem == this->cursorItem){
                QPoint mapCursorPressPos = cursorPressPos.toPoint();
                if(resizeType == 1){
                    YGraphicsItem::rerect(mapCursorPressPos);
                }else if(resizeType == 2){
                    if(lastPos.y() == mapCursorPressPos.y()) return;
                    lastPos = mapCursorPressPos;
                    QRect rect;
                    rect.setX(qMin(pos().toPoint().x(), startPos.x()));
                    rect.setY(qMin(startPos.y(), mapCursorPressPos.y()));
                    rect.setRight(qMax(pos().toPoint().x(), startPos.x()));
                    rect.setBottom(qMax(startPos.y(), mapCursorPressPos.y()));
                    rerect(rect);
                }else{
                    if(lastPos.x() == mapCursorPressPos.x()) return;
                    lastPos = mapCursorPressPos;
                    QRect rect;
                    rect.setX(qMin(startPos.x(), mapCursorPressPos.x()));
                    rect.setY(qMin(pos().toPoint().y(), startPos.y()));
                    rect.setRight(qMax(startPos.x(), mapCursorPressPos.x()));
                    rect.setBottom(qMax(pos().toPoint().y(), startPos.y()));
                    rerect(rect);
                }
                showChange();
            }else{
                resizeType = 0;
                this->cursorItem = nullptr;
                setFocus();
            }
        }else{
            this->cursorItem = cursorItem;
            int index = changeItem.indexOf(cursorItem);
            if(index < 4) resizeType = 1;
            else if(index < 6) resizeType = 2;
            else resizeType = 3;
            QRect rect = QRectF(pos(), this->rect().size()).toRect();
            switch(index){
            case 0: startPos = rect.bottomRight();break;
            case 1: startPos = rect.bottomLeft();break;
            case 2: startPos = rect.topRight();break;
            case 3: startPos = rect.topLeft();break;
            case 4: startPos = rect.bottomRight();break;
            case 5: startPos = rect.topRight();break;
            case 6: startPos = rect.bottomRight();break;
            case 7: startPos = rect.bottomLeft();break;
            }
        }
    }
    explicit YGraphicsRectItem(const QPointF &pos, const QSizeF& size): QGraphicsRectItem(QRectF(QPointF(0, 0), size)){
        setPos(pos);
        setAcceptHoverEvents(true);
        setBrush(Qt::NoBrush);
        setFlag(QGraphicsItem::ItemIsFocusable);
    }
    virtual void focusInEvent(QFocusEvent *){
        showChange();
    }
    virtual void focusOutEvent(QFocusEvent *){
        if(!inChangeHover()){
            hideChange();
        }
    }
    virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event){
        QPointF epos = event->pos();
        bool xValid = ((epos.x() >= rect().x() && epos.x() <= rect().x() + pen().width()) ||
                       (epos.x() <= rect().right() && epos.x() >= rect().right() - pen().width()));
        bool yValid = ((epos.y() >= rect().y() && epos.y() <= rect().y() + pen().width()) ||
                       (epos.y() <= rect().bottom() && epos.y() >= rect().bottom() - pen().width()));
        inChangeArea = (xValid || yValid);
        scene()->setProperty("inChangeArea", inChangeArea);
        if(inChangeArea){
            setCursor(Qt::SizeAllCursor);
        }else{
            unsetCursor();
        }
    }
    virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *){
        inChangeArea = false;
        scene()->setProperty("inChangeArea", inChangeArea);
        unsetCursor();
    }
    virtual void mousePressEvent(QGraphicsSceneMouseEvent *event){
        pressPos = event->pos();
        hideChange();
    }
    virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event){
        if(inChangeArea){
            if(resizeType == 0){
                QPointF pos = this->pos() - pressPos + event->pos();
                setPos(pos);
            }
        }
    }
    virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *){if(inChangeArea){reChangePos();showChange();}}
};
class YGraphicsEllipseItem : public QGraphicsEllipseItem, public YGraphicsItem
{
public:
    void rerect(const QRect& rect){
        if(changePos.empty()){
            changePos.resize(4);
        }
        setPos(rect.topLeft());
        setRect(QRectF(0, 0, rect.width(), rect.height()));
    }
    void reChangePos(){
        QRectF rect(pos(), this->rect().size());
        changePos[0] = QPointF(rect.x() + rect.width() / 2.0, rect.y());
        changePos[1] = QPointF(rect.x() + rect.width() / 2.0, rect.bottom());
        changePos[2] = QPointF(rect.x(), rect.y() + rect.height() / 2.0);
        changePos[3] = QPointF(rect.right(), rect.y() + rect.height() / 2.0);
    }
    void showChange(){
        static QCursor cursors[4] = {Qt::SizeVerCursor, Qt::SizeVerCursor, Qt::SizeHorCursor, Qt::SizeHorCursor};
        if(changeItem.empty()){
            for(int i = 0; i < changePos.size(); ++i){
                YGraphicsChangeItem* ellipse = new YGraphicsChangeItem;
                changeItem.append(ellipse);
                ellipse->setCursor(cursors[i]);
                ellipse->item = this;
                scene()->addItem(ellipse);
            }
        }
        reChangePos();
        for(int i = 0; i < changePos.size(); ++i){
            changeItem[i]->setPos(changePos.at(i));
            QPen pen = this->pen();
            pen.setWidth(1);
            changeItem[i]->setPen(pen);
            changeItem[i]->show();
        }
        showChanged = true;
    }
    void hideChange(){
        for(int i = 0; i < changePos.size(); ++i){
            changeItem[i]->hide();
        }
        showChanged = false;
    }
    void cursorChange(const QPointF& cursorPressPos, YGraphicsChangeItem* cursorItem){
        if(this->cursorItem){
            if(cursorItem == this->cursorItem){
                QPoint mapCursorPressPos = cursorPressPos.toPoint();
                if(resizeType == 1){
                    YGraphicsItem::rerect(mapCursorPressPos);
                }else if(resizeType == 2){
                    if(lastPos.y() == mapCursorPressPos.y()) return;
                    lastPos = mapCursorPressPos;
                    QRect rect;
                    rect.setX(qMin(pos().toPoint().x(), startPos.x()));
                    rect.setY(qMin(startPos.y(), mapCursorPressPos.y()));
                    rect.setRight(qMax(pos().toPoint().x(), startPos.x()));
                    rect.setBottom(qMax(startPos.y(), mapCursorPressPos.y()));
                    rerect(rect);
                }else{
                    if(lastPos.x() == mapCursorPressPos.x()) return;
                    lastPos = mapCursorPressPos;
                    QRect rect;
                    rect.setX(qMin(startPos.x(), mapCursorPressPos.x()));
                    rect.setY(qMin(pos().toPoint().y(), startPos.y()));
                    rect.setRight(qMax(startPos.x(), mapCursorPressPos.x()));
                    rect.setBottom(qMax(pos().toPoint().y(), startPos.y()));
                    rerect(rect);
                }
                showChange();
            }else{
                resizeType = 0;
                this->cursorItem = nullptr;
                setFocus();
            }
        }else{
            this->cursorItem = cursorItem;
            int index = changeItem.indexOf(cursorItem);
            if(index < 2) resizeType = 2;
            else resizeType = 3;
            QRect rect = QRectF(pos(), this->rect().size()).toRect();
            switch(index){
            case 0: startPos = rect.bottomRight();break;
            case 1: startPos = rect.topRight();break;
            case 2: startPos = rect.bottomRight();break;
            case 3: startPos = rect.bottomLeft();break;
            }
        }
    }
    explicit YGraphicsEllipseItem(const QPointF &pos, const QSizeF& size): QGraphicsEllipseItem(QRectF(QPointF(0, 0), size)){
        setPos(pos);
        setAcceptHoverEvents(true);
        setBrush(Qt::NoBrush);
        setFlag(QGraphicsItem::ItemIsFocusable);
    }
    virtual void focusInEvent(QFocusEvent *){
        showChange();
    }
    virtual void focusOutEvent(QFocusEvent *){
        if(!inChangeHover()){
            hideChange();
        }
    }
    virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event){
        QPointF epos = event->pos();
        QPainterPath path = shape();
        inChangeArea = (path.contains(epos) &&
                        !path.contains(QRectF(epos.x() - pen().width(), epos.y() - pen().width(),
                                              pen().width() + 1, pen().width() + 1)));
        scene()->setProperty("inChangeArea", inChangeArea);
        if(inChangeArea){
            setCursor(Qt::SizeAllCursor);
        }else{
            unsetCursor();
        }
    }
    virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *){
        inChangeArea = false;
        scene()->setProperty("inChangeArea", inChangeArea);
        unsetCursor();
    }
    virtual void mousePressEvent(QGraphicsSceneMouseEvent *event){
        pressPos = event->pos();
        hideChange();
    }
    virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event){
        if(inChangeArea){
            if(resizeType == 0){
                QPointF pos = this->pos() - pressPos + event->pos();
                setPos(pos);
            }
        }
    }
    virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *){if(inChangeArea){reChangePos();showChange();}}
};
class YGraphicsArrowItem : public QGraphicsPolygonItem, public YGraphicsItem
{
public:
    virtual void rerect(const QPoint& pos){
        if(lastPos == pos) return;
        lastPos = pos;
        makePath();
    }
    QPoint rotatePoint(const QPoint& pos, qreal cosTheta, qreal sinTheta, int positiveX, int positiveY)
    {
        return QPoint(qRound(pos.x() * cosTheta + pos.y() * sinTheta) * positiveX,
                      qRound(-pos.x() * sinTheta + pos.y() * cosTheta) * positiveY);
    }
    char AlignPadding_YGraphicsArrowItem[7];
    QVector<QPoint> points;
    void makePath(){
        QPoint pos = this->pos().toPoint();
        int x = qAbs(lastPos.x() - pos.x()), y = qAbs(lastPos.y() - pos.y());
        qreal rwidth = qSqrt(x*x+y*y), theta = qAcos(x / rwidth);
        int width = qRound(rwidth);
        int grade = pen().width();
        qreal cosTheta = qCos(theta);
        qreal sinTheta = qSin(theta);
        int positiveX = (lastPos.x() >= pos.x() ? 1 : -1), positiveY = (lastPos.y() <= pos.y() ? 1 : -1);

        int size = 11;
        if(width >= 30 && grade > 1) size = 15;
        if(width >= 40 && grade > 2) size = 21;

        if(points.size() != 6) points.resize(6);
        points[3] = rotatePoint(QPoint(width, 0), cosTheta, sinTheta, positiveX, positiveY);

        int wp = width - size, ah = size / 2, wh = ah / 2;
        points[1] = rotatePoint(QPoint(wp, -wh), cosTheta, sinTheta, positiveX, positiveY);
        points[2] = rotatePoint(QPoint(wp, -ah), cosTheta, sinTheta, positiveX, positiveY);
        points[4] = rotatePoint(QPoint(wp, ah), cosTheta, sinTheta, positiveX, positiveY);
        points[5] = rotatePoint(QPoint(wp, wh), cosTheta, sinTheta, positiveX, positiveY);
        setPolygon(QPolygon(points));
    }
    void reChangePos(){
        changePos[0] = pos();
        changePos[1] = QPointF(lastPos);
    }
    void showChange(){
        static QCursor cursors[2] = {Qt::SizeFDiagCursor, Qt::SizeBDiagCursor};
        if(changePos.empty()){
            changePos.resize(2);
        }
        if(changeItem.empty()){
            for(int i = 0; i < changePos.size(); ++i){
                YGraphicsChangeItem* ellipse = new YGraphicsChangeItem;
                changeItem.append(ellipse);
                ellipse->setCursor(cursors[i]);
                ellipse->item = this;
                scene()->addItem(ellipse);
            }
        }
        reChangePos();
        for(int i = 0; i < changePos.size(); ++i){
            changeItem[i]->setPos(changePos.at(i));
            changeItem[i]->setPen(QPen(brush().color(), 1));
            changeItem[i]->show();
        }
        showChanged = true;
    }
    void hideChange(){
        for(int i = 0; i < changePos.size(); ++i){
            changeItem[i]->hide();
        }
        showChanged = false;
    }
    void cursorChange(const QPointF& cursorPressPos, YGraphicsChangeItem* cursorItem){
        if(this->cursorItem){
            if(cursorItem == this->cursorItem){
                QPoint mapCursorPressPos = cursorPressPos.toPoint();
                if(resizeType == 1){
                    if(cursorPressPos == pos()) return;
                    setPos(cursorPressPos);
                    makePath();
                }else{
                    if(mapCursorPressPos == lastPos) return;
                    lastPos = mapCursorPressPos;
                    makePath();
                }
                showChange();
            }else{
                resizeType = 0;
                this->cursorItem = nullptr;
                setFocus();
            }
        }else{
            this->cursorItem = cursorItem;
            resizeType = changeItem.indexOf(cursorItem) + 1;
        }
    }
    explicit YGraphicsArrowItem(const QPointF &pos, const QSize&): QGraphicsPolygonItem(){
        setPos(pos);
        rerect(startPos);
        setAcceptHoverEvents(true);
        isArrow = true;
        points.resize(6);
        points[0] = QPoint(0, 0);
        setFlag(QGraphicsItem::ItemIsFocusable);
    }
    virtual void focusInEvent(QFocusEvent *){
        showChange();
    }
    virtual void focusOutEvent(QFocusEvent *){
        if(!inChangeHover()){
            hideChange();
        }
    }
    virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *){
        inChangeArea = true;
        scene()->setProperty("inChangeArea", inChangeArea);
        setCursor(Qt::SizeAllCursor);
    }
    virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *){
        inChangeArea = false;
        scene()->setProperty("inChangeArea", inChangeArea);
        unsetCursor();
    }
    virtual void mousePressEvent(QGraphicsSceneMouseEvent *event){
        pressPos = event->pos();
        hideChange();
    }
    virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event){
        if(inChangeArea){
            if(resizeType == 0){
                QPointF pos = this->pos() - pressPos + event->pos();
                lastPos =(QPointF(lastPos) - pressPos + event->pos()).toPoint();
                setPos(pos);
                makePath();
            }
        }
    }
    virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *){if(inChangeArea){showChange();}}
};
class YGraphicsPenItem : public QGraphicsPathItem
{
public:
};
class YGraphicsMosaicItem : public QGraphicsPixmapItem
{
public:
};
class YGraphicsTextItem : public QGraphicsTextItem
{
public:
};
class YGraphicsTipItem : public QGraphicsItem
{
public:
};
};
using namespace Y_Image_Drawer_GUI;


YImageDrawer::YImageDrawer(const QImage& image, QWidget *parent) : QWidget(parent), image(image)
{
    setFixedSize(image.size());
    scene = new QGraphicsScene(image.rect(), this);
    view = new QGraphicsView(scene, this);
    view->setStyleSheet("border: none;");

    view->setRenderHint(QPainter::Antialiasing);
    view->viewport()->installEventFilter(this);
    view->resize(image.size());
    scene->addPixmap(QPixmap::fromImage(image));
    scene->setProperty("inChangeArea", false);
    scene->setSceneRect(view->rect());
    //    scene->focusItem()->clearFocus();
}

void YImageDrawer::setViewCursor(const QCursor &cursor)
{
    view->setCursor(cursor);
}

void YImageDrawer::thicknessChanged(int thickness)
{
    QAbstractGraphicsShapeItem* item = dynamic_cast<QAbstractGraphicsShapeItem*>(scene->focusItem());
    if(item){
        QPen pen = item->pen();
        pen.setWidth(thickness);
        item->setPen(pen);
        if(dynamic_cast<YGraphicsItem*>(scene->focusItem())->isArrow){
            dynamic_cast<YGraphicsArrowItem*>(scene->focusItem())->makePath();
        }
        item->update();
    }
}

void YImageDrawer::colorChanged(QColor color)
{
    QAbstractGraphicsShapeItem* item = dynamic_cast<QAbstractGraphicsShapeItem*>(scene->focusItem());
    if(item){
        if(dynamic_cast<YGraphicsItem*>(scene->focusItem())->isArrow){
            item->setBrush(color);
        }else{
            QPen pen = item->pen();
            pen.setColor(color);
            item->setPen(pen);
        }
        item->update();
    }
}

void YImageDrawer::redo()
{
    if(items.isEmpty()) return;
    QGraphicsItem* qGraphicsItem = items.takeLast();
    scene->removeItem(qGraphicsItem);
    delete qGraphicsItem;
}

#include "DrawTools.h"
void YImageDrawer::createItem(const QPoint &pos)
{
    QGraphicsItem* qGraphicsItem = nullptr;
    switch(type){
    case Rect: {
        YGraphicsRectItem* item = new YGraphicsRectItem(pos, QSizeF(2, 2));
        qGraphicsItem = item;
        currentItem = item;
        item->setPen(QPen(tools->getColor(), tools->getThickness()));
    };break;
    case Ellipse: {
        YGraphicsEllipseItem* item = new YGraphicsEllipseItem(pos, QSize(2, 2));
        qGraphicsItem = item;
        currentItem = item;
        item->setPen(QPen(tools->getColor(), tools->getThickness()));
    };break;
    case Arrow: {
        YGraphicsArrowItem* item = new YGraphicsArrowItem(pos, QSize(2, 2));
        qGraphicsItem = item;
        currentItem = item;
        QPen pen = Qt::NoPen;
        pen.setWidth(tools->getThickness());
        item->setPen(pen);
        item->setBrush(tools->getColor());
    };break;
    default:return;
    }
    currentItem->startPos = pos;
    scene->addItem(qGraphicsItem);
    items.append(qGraphicsItem);
}

bool YImageDrawer::eventFilter(QObject *object, QEvent *event)
{
    QMouseEvent *mouseEvent = reinterpret_cast<QMouseEvent*>(event);
    if(event->type() == QEvent::MouseButtonPress){
        pressed = true;
        startPos = mouseEvent->pos();
    }else if(event->type() == QEvent::MouseMove){
        if(pressed && !scene->property("inChangeArea").toBool()){
            if(!currentItem){
                if(startPos != mouseEvent->pos())
                    createItem(startPos);
            }
            if(currentItem){
                currentItem->rerect(mouseEvent->pos());
            }
        }
    }else if(event->type() == QEvent::MouseButtonRelease){
        pressed = false;
        currentItem = nullptr;
    }
    return QWidget::eventFilter(object, event);
}
